
package org.movee.spring.data.hugegraph.repository.query;

import org.movee.spring.data.hugegraph.annotation.Query;
import org.movee.spring.data.hugegraph.mapping.HugeMappingContext;
import org.movee.spring.data.hugegraph.mapping.HugePersistentEntity;
import org.movee.spring.data.hugegraph.query.paramerter.HugeParameters;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.EntityMetadata;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.Optional;

public class HugeQueryMethod extends QueryMethod {

    private Method method;

    private HugeMappingContext mappingContext;

    private Optional<Query> query;

    private @Nullable
    HugeEntityMetadata<?> entityMetadata;

    public HugeQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
                           HugeMappingContext mappingContext) {
        super(method, metadata, factory);

        Assert.notNull(mappingContext, "MappingContext must not be null");

        verify(method, metadata);

        this.method = method;
        this.mappingContext = mappingContext;
        this.query = Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, Query.class));

    }

    @SuppressWarnings("unused")
    public void verify(Method method, RepositoryMetadata metadata) {

        if (isPageQuery()) {
            throw new InvalidDataAccessApiUsageException("Page queries are not supported.");
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public EntityMetadata<?> getEntityInformation() {
        if (this.entityMetadata == null) {

            Class<?> returnedObjectType = getReturnedObjectType();
            Class<?> domainClass = getDomainClass();

            if (ClassUtils.isPrimitiveOrWrapper(returnedObjectType)) {
                this.entityMetadata = new SimpleHugeEntityMetadata<>((Class<Object>) domainClass,
                        this.mappingContext.getPersistentEntity(domainClass));

            } else {
                // FIXME: 暂时跟上面一样
                HugePersistentEntity<?> returnedEntity = this.mappingContext.getPersistentEntity(returnedObjectType);
                this.entityMetadata = new SimpleHugeEntityMetadata<>((Class<Object>) returnedEntity.getType(),
                        returnedEntity);
            }
        }

        return this.entityMetadata;
    }

    /**
     * Returns whether the method has an annotated query.
     */
    public boolean hasAnnotatedQuery() {
        return this.query.map(Query::value).filter(StringUtils::hasText).isPresent();
    }

    /**
     * Returns the query string declared in a {@link Query} annotation or {@literal null} if neither the annotation found
     * nor the attribute was specified.
     *
     * @return the query string or {@literal null} if no query string present.
     */
    @Nullable
    public String getAnnotatedQuery() {
        return this.query.map(Query::value).orElse(null);
    }


    /**
     * Returns the required query string declared in a {@link Query} annotation or throws {@link IllegalStateException} if
     * neither the annotation found nor the attribute was specified.
     *
     * @return the query string.
     * @throws IllegalStateException in case query method has no annotated query.
     */
    public String getRequiredAnnotatedQuery() {
        return this.query.map(Query::value)
                .orElseThrow(() -> new IllegalStateException("Query method " + this + " has no annotated query"));
    }

    public Class getOriginReturnType() {
        return method.getReturnType();
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.repository.query.QueryMethod#getParameters()
     */
    @Override
    public HugeParameters getParameters() {
        return (HugeParameters) super.getParameters();
    }

    Optional<Query> getQueryAnnotation() {
        return this.query;
    }


    /* (non-Javadoc)
     * @see org.springframework.data.repository.query.QueryMethod#createParameters(java.lang.reflect.Method)
     */
    @Override
    protected HugeParameters createParameters(Method method) {
        return new HugeParameters(method);
    }

}
